﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Threading;
using SampleImageGenerator.Serialisation;
using SampleImageGenerator.Factories;
using LibNoise;
using SampleImageGenerator.Rendering;
using System.IO;
using SampleImageGenerator.Core.Events;

namespace SampleImageGenerator.Core
{
	internal class Engine : IEngine
	{
		private Boolean m_busy;
		private Object m_busyLock; 

		private Int32 m_progress;
		
		public event EventHandler<EventArgs> BusyChanged;
		public event EventHandler<EventArgs> ProgressChanged;

		public event EventHandler<ImageGeneratedEventArgs> ImageGenerated;
		public event EventHandler<ErrorEventArgs> ErrorOccurred; 
	
		public Engine()
		{
			m_busyLock = new Object(); 
		}

		public Boolean Busy
		{
			get
			{
				return m_busy;
			}

			protected set
			{
				if (m_busy != value)
				{
					m_busy = value;
					RaiseEvent<EventArgs>(BusyChanged, this, EventArgs.Empty);
				}
			}
		}

		public Int32 Progress
		{
			get
			{
				return m_progress;
			}

			protected set
			{
				if (m_progress != value)
				{
					m_progress = value;
					RaiseEvent<EventArgs>(ProgressChanged, this, EventArgs.Empty);
				}
			}
		}

		public Boolean CancellationRequested { get; protected set; }

		public void GenerateImageBatchAsync(BatchModeOptions options)
		{
			lock (m_busyLock)
			{
				if (Busy)
				{
					throw new InvalidOperationException("Engine is already busy.");
				}
				else
				{
					Busy = true;
					ThreadPool.QueueUserWorkItem(GenerateImageBatch, options);
				}
			}
		}

		public void GenerateImageAsync(String xml)
		{
			lock (m_busyLock)
			{
				if (Busy)
				{
					throw new InvalidOperationException("Engine is already busy.");
				}
				else
				{
					Busy = true;
					ThreadPool.QueueUserWorkItem(GenerateImage, xml);
				}
			}
		}

		public void RequestCancellationAsync()
		{
			CancellationRequested = true; 
		}

		private void GenerateImageBatch(Object state)
		{
			try
			{
				BatchModeOptions options = (BatchModeOptions)state;

				Int32 imageWidth = options.ImageWidth;
				Int32 imageHeight = options.ImageHeight;
				Int32 imageCount = options.ImageCount;
				Int32 seed = options.Seed;

				Size imageSize = new Size(imageWidth, imageHeight);
				Random random = new Random(seed);

				for (Int32 number = 0; number < imageCount; number++)
				{
					Int32 imageSeed = random.Next();

					String imageDefinition = GenerateImageDefinition(imageSize, imageSeed);
					
					GenerateImageInternal(imageDefinition);

					Double fractionComplete = (Double)(number + 1) / (Double)imageCount;
					Int32 percentageComplete = Convert.ToInt32(100.0 * fractionComplete);
					this.Progress = percentageComplete;

				}
			}
			catch (Exception ex)
			{
				OnError(ex);
			}
			finally
			{
				Progress = 0;
				Busy = false;
				CancellationRequested = false; 
			}
		}

		private String GenerateImageDefinition(Size imageSize, Int32 imageSeed)
		{
			ModuleFactory moduleFactory = new ModuleFactory();
			RendererFactory  rendererFactory = new RendererFactory();
			PaletteFactory paletteFactory = new PaletteFactory();
			
			SizeF regionSize = new SizeF(1, 1);
			PointF regionLocation = new PointF(-0.5f, -0.5f);
			RectangleF sourceRegion = new RectangleF(regionLocation, regionSize);

			IModule module = moduleFactory.GetModule(imageSeed, sourceRegion);
			IPalette palette = paletteFactory.GetPalette(imageSeed);
			IRenderer renderer = rendererFactory.GetRenderer(imageSeed);

			String id = imageSeed.ToString();

			ImageDefinition imageDefinition = new ImageDefinition(module, palette, renderer, sourceRegion, imageSize, id); 
			
			XmlSerialiser serialiser = new XmlSerialiser();
			StringWriter stringWriter = new StringWriter();
			serialiser.Serialise(stringWriter, imageDefinition);
			String imageDefinitionText = stringWriter.ToString();
			
			return imageDefinitionText;
		}

		private void GenerateImageInternal(String imageDefinitionXml)
		{
			Image image = null;
			try
			{
				XmlSerialiser serialiser = new XmlSerialiser();
				StringReader stringReader = new StringReader(imageDefinitionXml);
				ImageDefinition imageDefinition = (ImageDefinition)serialiser.Deserialise(stringReader);

				image = imageDefinition.Renderer.Render(
					imageDefinition.Module,
					imageDefinition.Palette,
					imageDefinition.SourceRegion,
					imageDefinition.ImageSize);

				if (CancellationRequested)
				{
					throw new OperationCanceledException();
				}

				OnImageGenerated(imageDefinition.Id, imageDefinitionXml, image);
			}
			finally
			{
				// Once the handlers have completed, we no longer need the image 
				// (the event arguments provide a copy to anyone that requests the
				// image), so dispose of it appropriately. 
				if (image != null)
				{
					image.Dispose();
				}
			}
		}

		private void GenerateImage(Object state)
		{
			try
			{
				String xml = (String)state;
				
				GenerateImageInternal(xml);
			}
			catch (Exception ex)
			{
				OnError(ex); 
			}
			finally
			{
				Progress = 0; 
				Busy = false;
				CancellationRequested = false;
			}
		}

		private void OnError(Exception ex)
		{
			ErrorEventArgs e = new ErrorEventArgs(ex);
			RaiseEvent<ErrorEventArgs>(ErrorOccurred, this, e);
		}

		private void OnImageGenerated(String id, String imageDefinition, Image image)
		{
			ImageGeneratedEventArgs e = new ImageGeneratedEventArgs(id, imageDefinition, image);
			RaiseEvent<ImageGeneratedEventArgs>(ImageGenerated, this, e);
		}

		private static void RaiseEvent<T>(EventHandler<T> handler, object sender, T e) where T : EventArgs
		{
			if (handler != null)
			{
				handler(sender, e);
			}
		}
	}
}
